Skip to content

feat: add transaction lifecycle callbacks#10144

Open
memleakd wants to merge 2 commits intocodeigniter4:4.8from
memleakd:feat/db-transaction-callbacks
Open

feat: add transaction lifecycle callbacks#10144
memleakd wants to merge 2 commits intocodeigniter4:4.8from
memleakd:feat/db-transaction-callbacks

Conversation

@memleakd
Copy link
Copy Markdown
Contributor

Introduction

Hello! I’d like to propose a small database-layer primitive for transaction-aware side effects.

This came up as a concrete need in one of my CodeIgniter 4 projects, where jobs, notifications, events, cache invalidation, and cleanup should happen only after a transaction has actually committed or rolled back.

I kept the implementation intentionally narrow: no queue integration, event dispatcher behavior, outbox abstraction, or configuration surface. Since I am new to contributing here, please treat this as an initial proposal, and apologies in advance if this has already been discussed, rejected, or does not fit the current roadmap.

Description

This PR adds database transaction lifecycle callbacks to database connections:

  • afterCommit() runs callbacks after the outermost transaction successfully commits.
  • afterRollback() runs callbacks after the outermost transaction rolls back.
  • Callbacks registered for the opposite outcome are discarded.
  • afterCommit() runs immediately when no transaction is active.
  • afterRollback() is a no-op when no transaction is active.
  • Callback exceptions bubble to the caller after the database transaction has already reached its final state.

Motivation

Application code can currently perform side effects inside a transaction, but that can be unsafe when the transaction later rolls back. For example, an application may dispatch a queued job, send a notification, publish an event, clear a cache entry, or trigger an external integration before the data it depends on is actually committed.

This can lead to race conditions or inconsistent behavior, such as:

  • A queued job runs before the committed row is visible.
  • A notification is sent for data that later rolls back.
  • A cache entry is invalidated even though the write did not persist.
  • Cleanup code runs even though the transaction eventually commits.

By adding transaction lifecycle callbacks to the database layer, applications and future framework libraries can safely defer these side effects until after the outermost transaction commits or rolls back.

Use Cases

Common use cases include:

  • Dispatching a queued job only after the database write it depends on has committed.
  • Sending a notification only after a transaction succeeds.
  • Publishing an application/domain event after committed data is visible.
  • Invalidating or warming cache entries after a successful commit.
  • Running cleanup logic only after a real rollback.
  • Supporting future framework features that need transaction-aware side effects.

Behavior Notes

Callbacks are tied to the outermost transaction. Nested transaction callbacks do not run until the outermost transaction commits or rolls back.

Only callbacks for the final transaction outcome run: commit discards rollback callbacks, and rollback discards commit callbacks.

If a callback throws, the exception bubbles to the caller after the database transaction has already committed or rolled back, so the callback exception does not change the database outcome.

Testing

Added live database tests covering commit callbacks, rollback callbacks, nested transactions, callback exceptions, and automatic rollback behavior.

Checklist

  • Securely signed commits
  • Component(s) with PHPDoc blocks, only if necessary or adds value (without duplication)
  • Unit testing, with >80% coverage
  • User guide updated
  • Conforms to style guide

@github-actions github-actions Bot added the 4.8 PRs that target the `4.8` branch. label Apr 25, 2026
Copy link
Copy Markdown
Member

@michalsn michalsn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like a useful feature.

We should add direct tests for commit and rollback callbacks when using a manual API (transBegin() / transCommit() / transRollback()).

Comment thread system/Database/BaseConnection.php
Comment thread user_guide_src/source/changelogs/v4.8.0.rst Outdated
Comment thread user_guide_src/source/database/transactions.rst
Comment thread user_guide_src/source/database/transactions.rst
- Add afterCommit() and afterRollback() callbacks to database connections
- Run callbacks only after the outermost transaction commits or rolls back
- Discard callbacks registered for the opposite transaction outcome
- Document callback exception behavior and automatic rollback handling
- Add live database coverage for callback ordering, nesting, and failures

Signed-off-by: memleakd <[email protected]>
@memleakd memleakd force-pushed the feat/db-transaction-callbacks branch from e1b5cb4 to f3181b2 Compare April 26, 2026 11:46
@memleakd
Copy link
Copy Markdown
Contributor Author

Thanks for the helpful review and suggestions, @michalsn!

I pushed f3181b2 to address the comments:

  • Added afterCommit() and afterRollback() to ConnectionInterface.
  • Added direct manual transaction tests for transBegin() / transCommit() and transBegin() / transRollback().
  • Added a changelog link to the user guide section.
  • Added a no-transaction example and documented the Model event caveat.
  • Added a warning that callback exceptions prevent later callbacks from running.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

4.8 PRs that target the `4.8` branch.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants